/*
 * macaroon.cpp
 *
 *  Created on: Mar 19, 2017
 *      Author: hp
 */

#include "macaroon.h"

#include <iostream>
#include <string.h>

#include <sodium/randombytes.h>
#include <sodium/crypto_box.h>

namespace c_macaroons {
#include "macaroons.h"
}

namespace macaroons {

macaroon::macaroon() {
	m = nullptr;
}

macaroon::macaroon(const macaroon& rhs) {
	c_macaroons::macaroon_returncode err = c_macaroons::MACAROON_SUCCESS;

	m = c_macaroons::macaroon_copy(rhs.m, &err);

	//TODO error handling
}

macaroon::macaroon(std::string location, std::string key, std::string id) {
	create(reinterpret_cast<const unsigned char*>(location.c_str()),
			location.size(),
			reinterpret_cast<const unsigned char*>(key.c_str()), key.size(),
			reinterpret_cast<const unsigned char*>(id.c_str()), id.size());
}

macaroon::macaroon(const char* location, size_t location_sz, const char* key,
		size_t key_sz, const char* id, size_t id_sz) {

	create(reinterpret_cast<const unsigned char*>(location), location_sz,
			reinterpret_cast<const unsigned char*>(key), key_sz,
			reinterpret_cast<const unsigned char*>(id), id_sz);
}

macaroon::macaroon(const unsigned char* location, size_t location_sz,
		const unsigned char* key, size_t key_sz, const unsigned char* id,
		size_t id_sz) {

	create(location, location_sz, key, key_sz, id, id_sz);
}

macaroon::macaroon(std::string data) {
	c_macaroons::macaroon_returncode err = c_macaroons::MACAROON_SUCCESS;

	m = c_macaroons::macaroon_deserialize((const unsigned char*) data.data(),
			data.size(), &err);

	if (err != c_macaroons::MACAROON_SUCCESS) {
		std::cerr << macaroon_error(err) << std::endl;
	}
	// TODO error handling
}

void macaroon::add_first_party_caveat(std::string predicate) {
	c_macaroons::macaroon_returncode err = c_macaroons::MACAROON_SUCCESS;
	c_macaroons::macaroon *n;

	n = c_macaroons::macaroon_add_first_party_caveat(m,
			reinterpret_cast<const unsigned char*>(predicate.c_str()),
			predicate.size(), &err);

	if (err != c_macaroons::MACAROON_SUCCESS) {
		std::cerr << macaroon_error(err) << std::endl;
	}

	c_macaroons::macaroon_destroy(m);
	m = n;
	// TODO error handling
}

void macaroon::add_first_party_caveat(const char* predicate,
		size_t predicate_sz) {
	c_macaroons::macaroon_returncode err = c_macaroons::MACAROON_SUCCESS;
	c_macaroons::macaroon *n;

	n = c_macaroons::macaroon_add_first_party_caveat(m,
			reinterpret_cast<const unsigned char*>(predicate), predicate_sz,
			&err);

	if (err != c_macaroons::MACAROON_SUCCESS) {
		std::cerr << macaroon_error(err) << std::endl;
	}

	c_macaroons::macaroon_destroy(m);
	m = n;
	// TODO error handling
}

void macaroon::add_third_party_caveat(std::string location, std::string key,
		std::string id) {
	c_macaroons::macaroon_returncode err = c_macaroons::MACAROON_SUCCESS;
	c_macaroons::macaroon *n;

	n = c_macaroons::macaroon_add_third_party_caveat(m,
			reinterpret_cast<const unsigned char*>(location.c_str()),
			location.size(),
			reinterpret_cast<const unsigned char*>(key.c_str()), key.size(),
			reinterpret_cast<const unsigned char*>(id.c_str()), id.size(),
			&err);

	if (err != c_macaroons::MACAROON_SUCCESS) {
		std::cerr << macaroon_error(err) << std::endl;
	}

	c_macaroons::macaroon_destroy(m);
	m = n;
	// TODO error handling
}

void macaroon::add_third_party_caveat(const char* location, size_t location_sz,
		const char *key, size_t key_sz, const char* id, size_t id_sz) {
	c_macaroons::macaroon_returncode err = c_macaroons::MACAROON_SUCCESS;
	c_macaroons::macaroon *n;

	n = c_macaroons::macaroon_add_third_party_caveat(m,
			reinterpret_cast<const unsigned char*>(location), location_sz,
			reinterpret_cast<const unsigned char*>(key), key_sz,
			reinterpret_cast<const unsigned char*>(id), id_sz, &err);

	if (err != c_macaroons::MACAROON_SUCCESS) {
		std::cerr << macaroon_error(err) << std::endl;
	}

	c_macaroons::macaroon_destroy(m);
	m = n;
	// TODO error handling
}

void macaroon::add_third_party_caveat(const char* location, size_t location_sz,
		const unsigned char *key, size_t key_sz, const unsigned char* id,
		size_t id_sz) {
	c_macaroons::macaroon_returncode err = c_macaroons::MACAROON_SUCCESS;
	c_macaroons::macaroon *n;

	n = c_macaroons::macaroon_add_third_party_caveat(m,
			reinterpret_cast<const unsigned char*>(location), location_sz, key,
			key_sz, id, id_sz, &err);

	if (err != c_macaroons::MACAROON_SUCCESS) {
		std::cerr << macaroon_error(err) << std::endl;
	}

	c_macaroons::macaroon_destroy(m);
	m = n;
	// TODO error handling
}

void macaroon::add_third_party_caveat_with_keypair(std::string location,
		unsigned char pk[crypto_box_PUBLICKEYBYTES],
		unsigned char sk[crypto_box_SECRETKEYBYTES]) {

	// secret for the remote
	unsigned char key[MACAROON_SUGGESTED_SECRET_LENGTH];
	randombytes(key, sizeof(key));

	unsigned char nonce[crypto_box_NONCEBYTES];
	randombytes(nonce, crypto_box_NONCEBYTES);

	unsigned char id[crypto_box_MACBYTES + sizeof(key) + sizeof(nonce)];
	crypto_box_easy(id, key, sizeof(key), nonce, pk, sk);
	memcpy(id + (crypto_box_MACBYTES + sizeof(key)), nonce, sizeof(nonce));

	add_third_party_caveat(location.c_str(), location.size(), key, sizeof(key),
			id, sizeof(id));
}

third_party_caveat_list macaroon::third_party_caveats() const {
	third_party_caveat_list ret;
	unsigned int num = c_macaroons::macaroon_num_third_party_caveats(m);

	for (unsigned int i = 0; i < num; ++i) {
		const unsigned char* location;
		size_t location_sz;
		const unsigned char* identifier;
		size_t identifier_sz;

		c_macaroons::macaroon_third_party_caveat(m, i, &location, &location_sz,
				&identifier, &identifier_sz);

		ret.push_back(
				{ std::string(reinterpret_cast<const char*>(location),
						location_sz), std::string(
						reinterpret_cast<const char*>(identifier),
						identifier_sz) });
	}

	return ret;
}

macaroon macaroon::prepare_for_request(const macaroon& d) const {
	c_macaroons::macaroon_returncode err = c_macaroons::MACAROON_SUCCESS;
	macaroon ret;

	ret.m = c_macaroons::macaroon_prepare_for_request(m, d.m, &err);

	return ret;
}

std::string macaroon::inspect() const {
	c_macaroons::macaroon_returncode err = c_macaroons::MACAROON_SUCCESS;

	size_t sz = c_macaroons::macaroon_inspect_size_hint(m);
	char* data = new char[sz];

	c_macaroons::macaroon_inspect(m, data, sz, &err);
	std::string ret(data);
	delete[] data;

	return ret;
	// TODO error handling
}

std::string macaroon::serialize() const {
	c_macaroons::macaroon_returncode err = c_macaroons::MACAROON_SUCCESS;
	size_t sz = c_macaroons::macaroon_serialize_size_hint(m,
			c_macaroons::MACAROON_V1);

	unsigned char *data = new unsigned char[sz];
	c_macaroons::macaroon_serialize(m, c_macaroons::MACAROON_V1, data, sz,
			&err);

	std::string ret((const char*) data);
	delete[] data;
	return ret;
	// TODO error handling
}

bool macaroon::validate() const {
	if (c_macaroons::macaroon_validate(m) == 0)
		return true;

	return false;
}

std::string macaroon::location() const {
	const unsigned char* location = NULL;
	size_t location_sz = 0;

	c_macaroons::macaroon_location(m, &location, &location_sz);
	return std::string((const char*) location, location_sz);
}

std::string macaroon::identifier() const {
	const unsigned char* identifier = NULL;
	size_t identifier_sz = 0;

	c_macaroons::macaroon_identifier(m, &identifier, &identifier_sz);
	return std::string((const char*) identifier, identifier_sz);
}

macaroon macaroon::operator=(const macaroon& rhs) {
	c_macaroons::macaroon_returncode err;
	m = c_macaroons::macaroon_copy(rhs.m, &err);
	return *this;
}

bool macaroon::operator==(const macaroon& rhs) const {
	if (c_macaroons::macaroon_cmp(m, rhs.m) == 0)
		return true;

	return false;
}

void macaroon::create(const unsigned char* location, size_t location_sz,
		const unsigned char* key, size_t key_sz, const unsigned char* id,
		size_t id_sz) {

	c_macaroons::macaroon_returncode err;
	m = c_macaroons::macaroon_create(location, location_sz, key, key_sz, id,
			id_sz, &err);
	// TODO error handler
}

bool macaroon::has_third_party_caveats() const {
	if (macaroon_num_third_party_caveats(m) > 0)
		return true;
	return false;
}

macaroon::~macaroon() {
	c_macaroons::macaroon_destroy(m);
}

macaroon macaroon_discharge(std::string location, std::string secret,
		unsigned char pk[crypto_box_PUBLICKEYBYTES],
		unsigned char sk[crypto_box_SECRETKEYBYTES]) {

	unsigned char key[MACAROON_SUGGESTED_SECRET_LENGTH];
	const unsigned char *key_s =
			reinterpret_cast<const unsigned char*>(secret.data());
	const unsigned char *nonce =
			reinterpret_cast<const unsigned char*>(secret.data())
					+ crypto_box_MACBYTES + sizeof(key);

	if (secret.size()
			!= (sizeof(key) + crypto_box_MACBYTES + crypto_box_NONCEBYTES)) {
		throw authentication_exception("Macaroon secret size mismatch");
	}

	int rc = crypto_box_open_easy(key, key_s, crypto_box_MACBYTES + sizeof(key),
			nonce, pk, sk);

	return macaroon(reinterpret_cast<const unsigned char*>(location.c_str()),
			location.size(), key, sizeof(key),
			reinterpret_cast<const unsigned char*>(secret.c_str()),
			secret.size());
}

} // namespace macaroons
